package gotnames.dm;

import gotnames.Utils;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.medallia.spider.DropdownElement;
import com.medallia.tiny.Clock;
import com.medallia.tiny.Format;
import com.medallia.tiny.Func;
import com.medallia.tiny.Strings;

/**
 * Stores various information about a user; a user can only belong to one
 * {@link Group}. {@link #getAuthToken()} is used to authenticate users and must
 * be globally unique (it is generated using {@link Utils#getUniqueId()} and a
 * unique check is thus not necessary. {@link #getEmail()} must be unqiue
 * within each group.
 */
@PersistenceCapable
public class User implements Serializable {
	private static final long serialVersionUID = 1L;

	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	private Long key;
	
	@Persistent
	private Long groupKey;

	@Persistent
	private String authToken;
	
	@Persistent
	private String firstName;
	
	@Persistent
	private String lastName;
	
	@Persistent
	private String email;
	
	@Persistent
	private boolean disableEmail;
	
	@Persistent
	private String profilePictureMd5;
	
	@Persistent
	private Integer profilePictureWidth;
	
	@Persistent
	private Integer profilePictureHeight;
	
	@Persistent
	private Date createdAt;
	
	@Persistent
	private Date lastLogin;
	
	@Persistent
	private boolean admin;
	
	public enum Gender {
		MALE,
		FEMALE,
		;
		
		@Override public String toString() {
			return Strings.capitalizeFirstCharacterLowercaseRest(name());
		}

		/** Case-insensitive version of {@link Gender#valueOf(String)}; returns null if the string does not match */
		public static Gender parse(String str) {
			try {
				return valueOf(str.toUpperCase());
			} catch (RuntimeException e) {
				return null;
			}
		}
	}
	
	@Persistent
	private Gender gender;
	
	@Persistent
	private Date lastGuess;
	
	@Persistent
	private int numGuesses;
	
	@Persistent
	private int numCorrectGuesses;
	
	public Long getKey() {
		return key;
	}

	/** The primary key of the {@link Group} this user is a member of */
	public Long getGroupKey() {
		return groupKey;
	}

	public void setGroupKey(Long groupKey) {
		this.groupKey = groupKey;
	}

	/** @return string used to authenticate users */
	public String getAuthToken() {
		return authToken;
	}

	public void setAuthToken(String authToken) {
		this.authToken = authToken;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	
	public String getFullName() {
		return getFirstName() + " " + getLastName();
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}
	
	/** @return true if no emails should be sent to this user */
	public boolean isDisableEmail() {
		return disableEmail;
	}

	public void setDisableEmail(boolean disableEmail) {
		this.disableEmail = disableEmail;
	}
	
	/** @return true if the user has a profile picture*/
	public boolean isProfilePicture() {
		return getProfilePictureMd5() != null;
	}

	/** @return the MD5 of the user's profile picture, or null if the user does not have a profile picture */
	public String getProfilePictureMd5() {
		return profilePictureMd5;
	}
	
	public int getProfilePictureWidth() { return profilePictureWidth; }
	public int getProfilePictureHeight() { return profilePictureHeight; }
	
	public int getProfilePictureWidthHalf() { return profilePictureWidth / 2; }
	public int getProfilePictureHeightHalf() { return profilePictureHeight / 2; }
	
	public int getProfilePictureWidthQuarter() { return profilePictureWidth / 4; }
	public int getProfilePictureHeightQuarter() { return profilePictureHeight / 4; }

	public void setProfilePictureInfo(ProfilePictureData profilePicture) {
		this.profilePictureMd5 = profilePicture.md5;
		this.profilePictureWidth = profilePicture.width;
		this.profilePictureHeight = profilePicture.height;
	}
	
	/** @return when this user was created */
	public Date getCreatedAt() {
		return createdAt;
	}

	public void setCreatedAt(Date createdAt) {
		this.createdAt = createdAt;
	}

	/** @return the date this user last logged in */
	public Date getLastLogin() {
		return lastLogin;
	}

	public void setLastLogin(Date lastLogin) {
		this.lastLogin = lastLogin;
	}

	public boolean isAdmin() {
		return admin;
	}

	public void setAdmin(boolean admin) {
		this.admin = admin;
	}

	public Gender getGender() {
		return gender;
	}

	public void setGender(Gender gender) {
		this.gender = gender;
	}
	
	/** @return the date this user last made a guess */
	public Date getLastGuess() {
		return lastGuess;
	}

	/** @return the number of guesses made by this user */
	public int getNumGuesses() {
		return numGuesses;
	}
	
	/** @return number of correct guesses */
	public int getNumCorrectGuesses() {
		return numCorrectGuesses;
	}
	
	public String getPstCorrectGuesses() {
		double pstCorrect = getPstCorrectGuessesDouble();
		return Double.isNaN(pstCorrect) ? "-" : Format.DF_PERCENT.format(pstCorrect);
	}

	public double getPstCorrectGuessesDouble() {
		return (double) getNumCorrectGuesses() / getNumGuesses();
	}
	
	/** Similar to {@link Guess#registerGuess(Date, boolean)}, but updates the info on this user */
	public void registerGuess(Date now, boolean correctGuess) {
		lastGuess = now;
		numGuesses++;
		
		if (correctGuess)
			numCorrectGuesses++;
	}

	public List<DropdownElement> getGenderDropdown() {
		return DropdownElement.fromEnum(Gender.class, getGender());
	}

	/** Create a new user in the given group */
	public static User newUser(Long groupKey) {
		User user = new User();
		user.setGroupKey(Utils.notNull(groupKey, "Group key is required"));
		user.setCreatedAt(Clock.now());
		
		String authToken = Utils.getUniqueId();
		user.setAuthToken(authToken);
		
		return user;
	}
	
	/** {@link Func} which alls {@link User#getKey()} */
	public static final Func<User, Long> KEY_FUNC = new Func<User, Long>() {
		@Override public Long call(User u) {
			return u.getKey();
		}
	};

	/** @return the user with the given email or null if there is no such user */
	public static User getByEmail(PersistenceManager pm, String email) {
		return QueryBuilder.begin(pm, User.class).getSingleByField("email", email);
	}

	/**
	 * Check if the given email can be set on the given user; throws
	 * {@link IllegalArgumentException} if not (typically because it is already
	 * in use by another user)
	 */
	public static void checkCanSetEmail(PersistenceManager pm, User u, String email) {
		User existingUser = User.getByEmail(pm, email);
		if (existingUser != null && !existingUser.getKey().equals(u.getKey()))
			throw new IllegalArgumentException("Email " + email + " is already in use by " + existingUser.getFullName() + ", cannot assign to " + u.getFullName());
	}
	
}
